﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TfsSpam.Differ
{
    /// <summary>
    /// The DifferenceLineList class provides a specialization of the
    /// generic List class with some helper properties for finding lines
    /// in the list.
    /// </summary>
    internal class DifferenceLineList : List<DifferenceLine>
    {
        #region -- Public class constants
        /// <summary>
        /// The value returned when a line number can't be determined.
        /// </summary>
        public const long InvalidLineNumber = long.MinValue;
        #endregion

        #region -- Constructors
        /// <summary>
        /// Initializes a new instance of the class that is empty 
        /// and has the default initial capacity.
        /// </summary>
        public DifferenceLineList()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the class that contains elements copied from 
        /// the specified collection and has sufficient capacity to accommodate the number 
        /// of elements copied.
        /// </summary>
        /// <param name="collection">The collection whose elements are copied to the new list.</param>
        public DifferenceLineList( IEnumerable<DifferenceLine> collection )
            : base( collection )
        {
        }
        #endregion

        #region -- Public lines properties
        /// <summary>
        /// The last line in the list that was added to the new file (if any).
        /// </summary>
        public DifferenceLine LastAddedLine
        {
            get { return FindLast( IsAddedLine ); }
        }

        /// <summary>
        /// The last deleted line in the list (if any).
        /// </summary>
        public DifferenceLine LastDeletedLine
        {
            get { return FindLast( IsDeletedLine ); }
        }

        /// <summary>
        /// The last context line from the new file (if any).
        /// </summary>
        public DifferenceLine LastNewContextLine
        {
            get { return FindLast( IsNewContextLine ); }
        }

        /// <summary>
        /// The first line from the new file in the list (if any).
        /// </summary>
        public DifferenceLine FirstLineFromNewFile
        {
            get { return Find( IsFromNewFile ); }
        }

        /// <summary>
        /// The deleted lines from the list.
        /// </summary>
        public List<DifferenceLine> DeletedLines
        {
            get { return FindAll( IsDeletedLine ); }
        }

        /// <summary>
        /// The added lines from the list.
        /// </summary>
        public List<DifferenceLine> AddedLines
        {
            get { return FindAll( IsAddedLine ); }
        }
        #endregion

        #region -- Public line number properties
        /// <summary>
        /// The line number of the last line in the list from
        /// the new file.
        /// </summary>
        /// <remarks>
        /// The value <see cref="InvalidLineNumber"/> is returned if
        /// the list doesn't contain any lines from the new file.
        /// </remarks>
        public long LastNewLineNumber
        {
            get
            {
                DifferenceLine lastNewContextLine = LastNewContextLine;
                DifferenceLine lastAddedLine = LastAddedLine;

                if ( lastNewContextLine != null && lastAddedLine != null )
                {
                    return Math.Max( lastNewContextLine.LineNumber, lastAddedLine.LineNumber );
                }
                else if ( lastNewContextLine != null )
                {
                    return lastNewContextLine.LineNumber;
                }
                else if ( lastAddedLine != null )
                {
                    return lastAddedLine.LineNumber;
                }
                else
                {
                    return InvalidLineNumber;
                }
            }
        }
        #endregion

        #region -- Public methods to retrieve lines
        /// <summary>
        /// Retrieves a line from the list that has the same line number
        /// and source as <paramref name="lineToGet"/> (if any).
        /// </summary>
        /// <param name="lineToGet">A line that identifies the line to retrieve.</param>
        /// <returns>
        /// A line with the same <see cref="LineSource"/> and <see cref="DifferenceLine.LineNumber"/>
        /// as <paramref name="lineToGet"/> or <c>null</c> if no such line can be found.
        /// </returns>
        public DifferenceLine GetLineFromSameSource( DifferenceLine lineToGet )
        {
            foreach ( DifferenceLine line in this )
            {
                if ( line.LineNumber == lineToGet.LineNumber && line.LineSource == lineToGet.LineSource )
                {
                    return line;
                }
            }

            return null;
        }

        /// <summary>
        /// Retrieves the line with the specified line number from the 
        /// requested source.
        /// </summary>
        /// <param name="source">The source of the line.</param>
        /// <param name="lineNumber">The number of the requested line.</param>
        /// <returns>The requested line or <c>null</c> if the line isn't present in the list.</returns>
        public DifferenceLine GetLine( LineSource source, long lineNumber )
        {
            foreach ( DifferenceLine line in this )
            {
                if ( line.LineSource == source && line.LineNumber == lineNumber )
                {
                    return line;
                }
            }

            return null;
        }

        /// <summary>
        /// Retrieves the lines from the specified source before (but not including)
        /// the requested line number.
        /// </summary>
        /// <param name="source">The source of the lines.</param>
        /// <param name="lineNumber">The number of the line that stops the retrieval.</param>
        /// <returns>A list of 0 or more lines from the list.</returns>
        public List<DifferenceLine> GetLinesBefore( LineSource source, long lineNumber )
        {
            List<DifferenceLine> lines = new List<DifferenceLine>();

            foreach ( DifferenceLine line in this )
            {
                if ( line.LineSource == source && line.LineNumber < lineNumber )
                {
                    lines.Add( line );
                }
            }

            return lines;
        }

        /// <summary>
        /// Retrieves the lines from the specified source after the requested
        /// line number.
        /// </summary>
        /// <param name="source">The source of the lines.</param>
        /// <param name="lineNumber">The number of the line that starts the retrieval.</param>
        /// <returns>A list of 0 or more lines from the list.</returns>
        public List<DifferenceLine> GetLinesAfter( LineSource source, long lineNumber )
        {
            List<DifferenceLine> lines = new List<DifferenceLine>();

            foreach ( DifferenceLine line in this )
            {
                if ( line.LineSource == source && line.LineNumber > lineNumber )
                {
                    lines.Add( line );
                }
            }

            return lines;
        }
        #endregion

        #region -- Find predicates
        /// <summary>
        /// This predicate used for the <see cref="Find"/> method
        /// identifies an added line in the list.
        /// </summary>
        /// <param name="line">The <see cref="DifferenceLine"/> object to check.</param>
        /// <returns>
        /// <c>true</c> if <paramref name="line"/> is an added line, <c>false</c>
        /// otherwise.
        /// </returns>
        private static bool IsAddedLine( DifferenceLine line )
        {
            return( line.LineType == LineType.Added );
        }

        /// <summary>
        /// This predicate used for the <see cref="Find"/> method
        /// identifies a context line from the new file.
        /// </summary>
        /// <param name="line">The <see cref="DifferenceLine"/> object to check.</param>
        /// <returns>
        /// <c>true</c> if <paramref name="line"/> originates from the new file and 
        /// is a context line, <c>false</c> otherwise.
        /// </returns>
        private static bool IsNewContextLine( DifferenceLine line )
        {
            return ( line.LineSource == LineSource.New && line.LineType == LineType.Context );
        }

        /// <summary>
        /// This predicate used for the <see cref="Find"/> method
        /// identifies a deleted line in the list.
        /// </summary>
        /// <param name="line">The <see cref="DifferenceLine"/> object to check.</param>
        /// <returns>
        /// <c>true</c> if <paramref name="line"/> is a deleted line, <c>false</c>
        /// otherwise.
        /// </returns>
        private static bool IsDeletedLine( DifferenceLine line )
        {
            return ( line.LineType == LineType.Deleted );
        }

        /// <summary>
        /// This predicate used for the <see cref="Find"/> method
        /// identifies a line from the new file in the list.
        /// </summary>
        /// <param name="line">The <see cref="DifferenceLine"/> object to check.</param>
        /// <returns>
        /// <c>true</c> if <paramref name="line"/> is from the new file, <c>false</c>
        /// otherwise.
        /// </returns>
        private static bool IsFromNewFile( DifferenceLine line )
        {
            return( line.LineSource == LineSource.New );
        }
        #endregion
    }
}
